from absl import flags
import collections
import itertools
import pandas
import json

SimParams = collections.namedtuple('SimParams',
                                   [
                                       'config_filename',
                                       'trade_subreq',
                                       'pick_symbol',
                                       'sim_type',
                                       'time_range',
                                       'latency_ms',
                                       'delay_accept_until_book',
                                       'threshold_bps',
                                       'cancel_threshold_bps',
                                       'taker_threshold_bps',
                                       'preserve_stack',
                                       'stack',
                                       'lot_size_mult',
                                       'neutral',
                                       'edge_bps',
                                       'edge_allowance_bps',
                                       'sticky_bp',
                                       'max_posting_period_sec',
                                       'order_update_period_sec',
                                       'basis_ma_window_min',
                                       'ref_feed_symbol',
                                       'trading_date',
                                       'use_predicted_latency',
                                       'latency_multiplier',
                                       'search_param_file',
                                       'window_period_sec',
                                       'enter_position_threshold',
                                       'max_std_ratio',
                                       'min_std_ratio',
                                       'max_lean_bp',
                                       'lean_edge_bp',
                                       'book_change_levels',
                                       'pos_lean_multiplier',
                                       'full_pos_lean_multiplier',
                                       'force_fire_threshold_bp',
                                       'price_pull_edge',
                                       'price_push_edge',
                                       'initial_position',
                                   ])


def define_sim_flags():
  allflags = set(flags.FLAGS)
  for fieldname in SimParams._fields:
    if not fieldname in allflags:
      flags.DEFINE_string(fieldname, None, '')
  flags.DEFINE_boolean('use_slurm', None, '')
  flags.DEFINE_string('run_nth', None, '')

  flags.FLAGS.use_predicted_latency = '1'
  flags.FLAGS.sim_type = 'lm_sim'
  flags.FLAGS.neutral = True


def split_or_default(value, default, atype):
  return [default] if value is None else [atype(avalue) for avalue in value.split(",")]


def get_paramater_list_dict():
  return {
      'order_update_period_sec':
          split_or_default(flags.FLAGS.order_update_period_sec, None, str),
      'lean_edge_bp':
          split_or_default(flags.FLAGS.lean_edge_bp, None, str),
      'latency_ms':
          split_or_default(flags.FLAGS.latency_ms, None, float),
      'latency_multiplier':
          split_or_default(flags.FLAGS.latency_multiplier, None, float),
      'threshold_bps':
          split_or_default(flags.FLAGS.threshold_bps, None, float),
      'cancel_threshold_bps':
          split_or_default(flags.FLAGS.cancel_threshold_bps, None, float),
      'taker_threshold_bps':
          split_or_default(flags.FLAGS.taker_threshold_bps, None, float),
      'preserve_stack':
          split_or_default(flags.FLAGS.preserve_stack, False, lambda x: bool(int(x))),
      'stack': [None] if flags.FLAGS.stack is None else flags.FLAGS.stack.split(","),
      'lot_size_mult':
          split_or_default(flags.FLAGS.lot_size_mult, None, float),
      'delay_accept_until_book':
          split_or_default(flags.FLAGS.delay_accept_until_book, 0, lambda x: int(x)),
      'edge_bps': [None] if flags.FLAGS.edge_bps is None else flags.FLAGS.edge_bps.split(","),
      'edge_allowance_bps': [None] if flags.FLAGS.edge_allowance_bps is None else
                            flags.FLAGS.edge_allowance_bps.split(","),
      'sticky_bp': [None] if flags.FLAGS.sticky_bp is None else flags.FLAGS.sticky_bp.split(","),
      'max_posting_period_sec': [None] if flags.FLAGS.max_posting_period_sec is None else
                                flags.FLAGS.max_posting_period_sec.split(","),
      'basis_ma_window_min': [None] if flags.FLAGS.basis_ma_window_min is None else
                             flags.FLAGS.basis_ma_window_min.split(","),
      'ref_feed_symbol': [None] if flags.FLAGS.ref_feed_symbol is None else
                         flags.FLAGS.ref_feed_symbol.split(","),
  }


def get_sim_params_from_flags():
  assert flags.FLAGS.config_filename, '--config_filename must be specified.'

  list_params = set(get_paramater_list_dict().keys())

  simparams_dict = {}
  for fieldname in SimParams._fields:
    if fieldname not in list_params:
      simparams_dict[fieldname] = getattr(flags.FLAGS, fieldname)
    else:
      simparams_dict[fieldname] = None
  # hack
  simparams_dict['trading_date'] = None
  return SimParams(**simparams_dict)


def get_search_params_from_file(file_path):
  with open(file_path, 'r') as fi:
    params = json.load(fi)
  param_list_dict = {k: v if isinstance(v, list) else [v] for k, v in params.items()}
  keys, values = zip(*param_list_dict.items())
  sparams = [dict(zip(keys, v)) for v in itertools.product(*values)]
  print(pandas.DataFrame(sparams).to_string())
  return sparams


def get_search_params():
  param_list_dict = get_paramater_list_dict()
  keys, values = zip(*param_list_dict.items())
  sparams = [dict(zip(keys, v)) for v in itertools.product(*values)]
  print(pandas.DataFrame(sparams).to_string())
  return sparams


def simparam_key(key):
  importance_order = [
      'sim_type',
      'trade_subreq',
      'stratname',
      'configname',
      'pick_symbol',
      'preserve_stack',
      'stack',
      'threshold_bps',
      'cancel_threshold_bps',
      'taker_threshold_bps',
      'use_predicted_latency',
      'latency_ms',
      'latency_multiplier',
      'delay_accept_until_book',
      'lot_size_mult',
      'neutral',
      'edge_bps',
      'edge_allowance_bps',
      'lean_edge_bp',
      'sticky_bp',
      'max_posting_period_sec',
      'order_update_period_sec',
      'basis_ma_window_min',
      'window_period_sec',
      'enter_position_threshold',
      'max_std_ratio',
      'min_std_ratio',
      'max_lean_bp',
      'book_change_levels',
      'pos_lean_multiplier',
      'full_pos_lean_multiplier',
      'force_fire_threshold_bp',
      'price_pull_edge',
      'price_push_edge',
      'initial_position',
      'ref_feed_symbol',
      'trading_date',
      'time_range',
  ]
  if key[0] in importance_order:
    return (importance_order.index(key[0]), key[0])
  else:
    return (1e4, key[0])


def get_abstract_name(name):
  return ''.join(filter(str.isupper, name.title()))


def basis_smm_flags():
  return [
      'window_period_sec',
      'enter_position_threshold',
      'max_std_ratio',
      'min_std_ratio',
      'max_lean_bp',
      'book_change_levels',
      'pos_lean_multiplier',
      'full_pos_lean_multiplier',
      'force_fire_threshold_bp',
      'price_pull_edge',
      'price_push_edge'
  ]


class ParamLabel(object):
  def __init__(self, sim_params):
    self.sim_params = sim_params._asdict()
    self.sim_params['configname'] = self.sim_params['config_filename'].split("/")[-1].replace(
        ".json", "")
    del self.sim_params['config_filename']
    if self.sim_params['sim_type'] == 'lm_sim':
      for basis_smm_flag in basis_smm_flags():
        del self.sim_params[basis_smm_flag]

  def get_name_params(self, run_params=None):
    if run_params is None:
      return dict(self.sim_params)
    else:
      return dict(self.sim_params, **run_params)

  def get_name_items(self, run_params=None):
    return [(key, value.strftime("%Y%m%d") if key == 'trading_date' else value)
            for key,
            value in sorted(self.get_name_params(run_params).items(), key=simparam_key)
            if value is not None]

  def get_name_str(self, run_params=None):
    return "__".join([
        "%s_%s" % (get_abstract_name(key), value) for key, value in self.get_name_items(run_params)
    ])

  def get_filename_str(self, *args, **kwargs):
    namestr = self.get_name_str(*args, **kwargs)
    if len(namestr) > 150:
      import hashlib
      m = hashlib.md5()
      m.update(namestr[150:].encode('utf-8'))
      namestr = namestr[:150] + "_" + self.sim_params['trading_date'].strftime(
          "%Y%m%d") + "_" + self.sim_params['time_range'] + "_" + m.hexdigest()
    return namestr
